import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Map;

import com.speljohan.rsbot.accessors.Player;
import com.speljohan.rsbot.bot.Bot;
import com.speljohan.rsbot.event.listeners.PaintListener;
import com.speljohan.rsbot.script.Constants;
import com.speljohan.rsbot.script.Methods;
import com.speljohan.rsbot.script.Script;
import com.speljohan.rsbot.script.wrappers.RSInterface;
import com.speljohan.rsbot.script.wrappers.RSObject;
import com.speljohan.rsbot.script.wrappers.RSPlayer;
import com.speljohan.rsbot.script.wrappers.RSTile;

public class VirtualMiner extends Script implements PaintListener {
	// numeric identifiers
	public static final byte TIN = 1;
	public static final byte COPPER = 1;
	public static final byte IRON = 2;
	public static final byte SILVER = 3;
	public static final byte COAL = 4;
	public static final byte GOLD = 5;
	public static final byte MITHRIL = 6;
	public static final byte ADAMANT = 7;
	public static final byte RUNE = 8;
	public byte best = 0;

	public static final int[] pickaxeTypes = new int[] { 1275, 1265, 1269,
			1267, 1273, 1271 };
	public static final int[] pickaxeHeads = new int[] { 480, 482, 484, 486,
			488, 490 };

	public static final int[] copper = new int[] { 3042, 11936, 11937, 11938,
			9708, 9709, 9710, 31080, 31081, 31082, 11960, 11961, 11962 };
	public static final int[] tin = new int[] { 3043, 11933, 11934, 11935,
			9714, 9715, 9716, 31077, 31078, 31079, 11957, 11958, 11959 };
	public static final int[] iron = new int[] { 9717, 9718, 9719, 31073,
			11954, 11955, 11956 };
	public static final int[] silver = new int[] { 11949, 11950, 37304, 37305 };
	public static final int[] coal = new int[] { 31070, 31071, 31068, 31069,
			11930, 11932 };
	public static final int[] gold = new int[] { 9722, 9720, 37310, 37313 };
	public static final int[] mithril = new int[] { 11942, 11944 };
	public static final int[] adamant = new int[] { 11939, 11941 };
	public static final int[] rune = new int[] {};
	public static final int[] empty = new int[] { 31059, 31060, 31061, 9724,
			9723, 9725 };
	public static final int[] smoking = new int[] { 11177, 11178, 11179, 11180,
			11181, 11182 };

	RSTile[] rockLocations;
	int[] timeFull;
	byte[] rockType;
	boolean typesComplete = false;
	RSTile startTile;

	boolean dropping = false;
	RSObject myRock = null;
	int lastinv;

	public int[] droppable = new int[] { 438, 434, 436, 440, 444 };

	int origExp;
	int oresMined = 0;
	long startTime;

	@Override
	public boolean atInventoryItem(final int itemLoc, final String option) {
		if (getCurrentTab() != Constants.TAB_INVENTORY
				&& !RSInterface.getInterface(Constants.INTERFACE_BANK)
						.isValid()
				&& !RSInterface.getInterface(Constants.INTERFACE_STORE)
						.isValid()) {
			openTab(Constants.TAB_INVENTORY);
		}
		final Point t = getInventoryItemPoint(itemLoc);
		clickMouse(t, 5, 5, false);
		return atMenu(option);
	}

	public boolean definatelyAtObject(final RSObject o, final String action) {
		boolean success = atObject(o, action);
		if (!success) {
			atTile(o.getLocation(), action);
			success = atObject(o, action);
		}
		if (!success) {
			walkTileMM(getClosestTile(o));
			success = atObject(o, action);
		}
		return success;
	}

	public boolean dropItem() {
		final boolean foundOne = false;
		for (int i = 0; i < 28; i++) {
			for (final int item : droppable) {
				if (item == getInventoryItems()[i].getID()) {
					atInventoryItem(i, "Drop");
				}
			}
		}
		if (!foundOne) {
			return false;
		}
		return true;
	}

	public RSTile[] findRocksWithin(final int range) {
		final int[] allrocks = new int[VirtualMiner.copper.length
				+ VirtualMiner.tin.length + VirtualMiner.iron.length
				+ VirtualMiner.silver.length + VirtualMiner.coal.length
				+ VirtualMiner.gold.length + VirtualMiner.mithril.length
				+ VirtualMiner.adamant.length + VirtualMiner.rune.length
				+ VirtualMiner.empty.length];
		System.arraycopy(VirtualMiner.copper, 0, allrocks, 0,
				VirtualMiner.copper.length);
		System.arraycopy(VirtualMiner.tin, 0, allrocks,
				VirtualMiner.copper.length, VirtualMiner.tin.length);
		System.arraycopy(VirtualMiner.iron, 0, allrocks,
				VirtualMiner.copper.length + VirtualMiner.tin.length,
				VirtualMiner.iron.length);
		System.arraycopy(VirtualMiner.silver, 0, allrocks,
				VirtualMiner.copper.length + VirtualMiner.tin.length
						+ VirtualMiner.iron.length, VirtualMiner.silver.length);
		System
				.arraycopy(VirtualMiner.coal, 0, allrocks,
						VirtualMiner.copper.length + VirtualMiner.tin.length
								+ VirtualMiner.iron.length
								+ VirtualMiner.silver.length,
						VirtualMiner.coal.length);
		System.arraycopy(VirtualMiner.gold, 0, allrocks,
				VirtualMiner.copper.length + VirtualMiner.tin.length
						+ VirtualMiner.iron.length + VirtualMiner.silver.length
						+ VirtualMiner.coal.length, VirtualMiner.gold.length);
		System.arraycopy(VirtualMiner.mithril, 0, allrocks,
				VirtualMiner.copper.length + VirtualMiner.tin.length
						+ VirtualMiner.iron.length + VirtualMiner.silver.length
						+ VirtualMiner.coal.length + VirtualMiner.gold.length,
				VirtualMiner.mithril.length);
		System.arraycopy(VirtualMiner.adamant, 0, allrocks,
				VirtualMiner.copper.length + VirtualMiner.tin.length
						+ VirtualMiner.iron.length + VirtualMiner.silver.length
						+ VirtualMiner.coal.length + VirtualMiner.gold.length
						+ VirtualMiner.mithril.length,
				VirtualMiner.adamant.length);
		System
				.arraycopy(VirtualMiner.rune, 0, allrocks,
						VirtualMiner.copper.length + VirtualMiner.tin.length
								+ VirtualMiner.iron.length
								+ VirtualMiner.silver.length
								+ VirtualMiner.coal.length
								+ VirtualMiner.gold.length
								+ VirtualMiner.mithril.length
								+ VirtualMiner.adamant.length,
						VirtualMiner.rune.length);
		System.arraycopy(VirtualMiner.empty, 0, allrocks,
				VirtualMiner.copper.length + VirtualMiner.tin.length
						+ VirtualMiner.iron.length + VirtualMiner.silver.length
						+ VirtualMiner.coal.length + VirtualMiner.gold.length
						+ VirtualMiner.mithril.length
						+ VirtualMiner.adamant.length
						+ VirtualMiner.rune.length, VirtualMiner.empty.length);
		final ArrayList<RSTile> array = new ArrayList<RSTile>();
		final int minX = getMyPlayer().getLocation().getX() - range;
		final int minY = getMyPlayer().getLocation().getY() - range;
		final int maxX = getMyPlayer().getLocation().getX() + range;
		final int maxY = getMyPlayer().getLocation().getY() + range;
		for (int x = minX; x < maxX; x++) {
			for (int y = minY; y < maxY; y++) {
				final RSObject o = getObjectAt(x, y);
				if (o != null) {
					for (final int id : allrocks) {
						if (o.getID() == id) {
							array.add(o.getLocation());
						}
					}
				}
			}
		}
		return array.toArray(new RSTile[0]);
	}

	@Override
	public String getAuthor() {
		return "VirtualMaster";
	}

	public RSObject getBestRock() {
		final float[] prefs = new float[rockType.length];
		for (int i = 0; i < prefs.length; i++) {
			final float distance = 1 / (float) (getRealDistanceTo(
					rockLocations[i], true) + 1);
			float level = 0;
			if (rockType[i] <= best && rockType[i] != 0) {
				level = (float) rockType[i] / (float) best;
			}
			final float players = 1 / (float) (getPlayersByRock(rockLocations[i]) + 1);
			prefs[i] = (float) (distance * 0.1 + level * 0.75 + players * 0.15);
			if (isEmpty(i) || isSmoking(getObjectAt(rockLocations[i]))) {
				prefs[i] = 0;
			}
		}
		int best = 0;
		float bestPref = 0;
		for (int i = 0; i < prefs.length; i++) {
			if (prefs[i] > bestPref) {
				best = i;
				bestPref = prefs[i];
			}
		}
		return getObjectAt(rockLocations[best]);
	}

	public RSTile getClosestTile(final RSObject o) {
		final RSTile object = o.getLocation();
		final RSTile xu = new RSTile(object.getX() + 1, object.getY());
		final RSTile xd = new RSTile(object.getX() - 1, object.getY());
		final RSTile yu = new RSTile(object.getX(), object.getY() + 1);
		final RSTile yd = new RSTile(object.getX(), object.getY() - 1);
		RSTile closest = xu;
		if (getRealDistanceTo(xd, false) < getRealDistanceTo(closest, false)) {
			closest = xd;
		}
		if (getRealDistanceTo(yu, false) < getRealDistanceTo(closest, false)) {
			closest = yu;
		}
		if (getRealDistanceTo(yd, false) < getRealDistanceTo(closest, false)) {
			closest = yd;
		}
		return closest;

	}

	@Override
	public String getName() {
		return "Virtualmaster's Powerminer";
	}

	public int getPlayersByRock(final RSTile rock) {
		int playerCount = 0;
		final int[] validPlayers = Bot.getClient().getPlayerIndexArray();
		final Player[] players = Bot.getClient().getPlayerArray();

		for (final int element : validPlayers) {
			if (players[element] == null) {
				continue;
			}
			final RSPlayer player = new RSPlayer(players[element]);
			try {
				final int distance = Methods.distanceBetween(rock, player
						.getLocation());
				if (distance == 1) {
					playerCount++;
				}
			} catch (final Exception e) {
			}
		}
		return playerCount;
	}

	@Override
	public String getScriptCategory() {
		return "Mining";
	}

	@Override
	public String getScriptDescription() {
		return "<html>\n" + "<head></head>\n" + "<body>\n" + "<center>"
				+ "<h2>" + getName() + "</h2>" + "</center>\n"
				+ "<b>Author:</b> " + getAuthor() + "<br />"
				+ "<b>Version:</b> " + getVersion() + "<br />"
				+ "<b>Features</b><br />"
				+ "Identifies entire mine of rocks while starting up.<br />"
				+ "Mines the best rock that your level allows.<br />"
				+ "Avoids other players mining if possible.<br />"
				+ "Avoids smoking rocks.<br />" + "</body>\n" + "</html>";
	}

	@Override
	public double getVersion() {
		return 1.0;
	}

	public boolean isEmpty(final int index) {
		try {
			final int id = getObjectAt(rockLocations[index]).getID();
			for (final int pid : VirtualMiner.empty) {
				if (id == pid) {
					return true;
				}
			}
			return false;
		} catch (final Exception e) {
		}
		return true;
	}

	public boolean isSmoking(final RSObject o) {
		try {
			final int id = o.getID();
			for (final int pid : VirtualMiner.smoking) {
				if (id == pid) {
					return true;
				}
			}
			return false;
		} catch (final Exception e) {
		}
		return true;
	}

	// Update the ore count, wait if we're mining, or find a new rock.
	@Override
	public int loop() {
		if (getInventoryCount() > lastinv) {
			oresMined++;
		}
		lastinv = getInventoryCount();
		try {
			if (getMyPlayer().getAnimation() >= 624
					&& getMyPlayer().getAnimation() <= 630
					|| getMyPlayer().isMoving()) {
				passiveLoop();
				return random(50, 150);
			}
			if (dropping) {
				dropping = dropItem();
				return random(250, 300);
			} else {
				if (getInventoryCount() == 28) {
					dropping = true;
					return random(400, 450);
				}
				myRock = getBestRock();
				definatelyAtObject(myRock, "Mine");
				return random(400, 450);
			}
		} catch (final Throwable t) {
			log("Yikes! An error occurred... Dumping then sleeping for 1 minute.");
			t.printStackTrace();
			return 60000;
		}
	}

	@Override
	public void onFinish() {
		Bot.getEventManager().removeListener(PaintListener.class, this);
	}

	@Override
	public void onRepaint(final Graphics g) {
		if (isLoggedIn() && distanceTo(startTile) <= 40) {
			g.setColor(Color.red);
			for (final RSTile o : rockLocations) {
				final Point rect = worldToMinimap(o.getX(), o.getY());
				g.drawRect(rect.x - 1, rect.y - 1, 3, 3);
			}
			g.setColor(Color.yellow);
			try {
				final RSTile o = myRock.getLocation();
				final Point rect = worldToMinimap(o.getX(), o.getY());
				g.drawRect(rect.x - 1, rect.y - 1, 3, 3);
			} catch (final Exception e) {
			}
			g.setColor(Color.green);
			final RSTile o = getBestRock().getLocation();
			final Point rect = worldToMinimap(o.getX(), o.getY());
			g.drawRect(rect.x - 1, rect.y - 1, 3, 3);
			g.setColor(Color.red);
			g
					.drawString(
							"Exp gained: "
									+ (skills
											.getCurrentSkillExp(Constants.STAT_MINING) - origExp)
									+ "  Ores Mined: "
									+ oresMined
									+ "  Percent to level "
									+ (skills
											.getCurrentSkillLevel(Constants.STAT_MINING) + 1)
									+ ": "
									+ skills
											.getPercentToNextLevel(Constants.STAT_MINING)
									+ "%", 5, 333);
		}
	}

	@Override
	public boolean onStart(final Map<String, String> args) {
		if (!isLoggedIn()) {
			log("Please log in to start this script.");
			return false;
		}
		// Identify what rock to mine
		passiveUpdating();
		// Scan the area for rocks
		rockLocations = findRocksWithin(40);
		if (rockLocations.length == 0) {
			log("No known Rocks found, Quitting...");
			return false;
		}

		log(rockLocations.length + " rocks found.");
		// Initialize rockType array
		rockType = new byte[rockLocations.length];
		// Scan all rocks for their rock type, basing off their id. If empty,
		// they'll be updated in a future sweep.
		updateTypes();
		if (typesComplete) {
			log("Mine fully discovered.");
		}
		// myRock cant be null, it's needed for the onRepaint functions.
		myRock = getObjectAt(rockLocations[0]);
		// initialize counters and such
		origExp = skills.getCurrentSkillExp(Constants.STAT_MINING);
		startTime = System.currentTimeMillis();
		lastinv = getInventoryCount();
		startTile = getMyPlayer().getLocation();
		return true;
	}

	// Used when we are already doing something in the script.
	public void passiveLoop() {
		if (!typesComplete) {
			updateTypes();
			if (typesComplete) {
				log("Mine fully discovered.");
			}
		}
		if (isSmoking(myRock)) {
			myRock = getBestRock();
			definatelyAtObject(myRock, "Mine");
		}
		passiveUpdating();
	}

	public void passiveUpdating() {
		final int level = skills.getCurrentSkillLevel(Constants.STAT_MINING);
		if (level >= 85) {
			best = VirtualMiner.RUNE;
		} else if (level >= 70) {
			best = VirtualMiner.ADAMANT;
		} else if (level >= 55) {
			best = VirtualMiner.MITHRIL;
		} else if (level >= 40) {
			best = VirtualMiner.GOLD;
		} else if (level >= 30) {
			best = VirtualMiner.COAL;
		} else if (level >= 20) {
			best = VirtualMiner.SILVER;
		} else if (level >= 15) {
			best = VirtualMiner.IRON;
		} else {
			best = VirtualMiner.COPPER;
		}
	}

	public void updateTypes() {
		boolean complete = true;
		for (int i = 0; i < rockType.length; i++) {
			if (rockType[i] == 0) {
				final int id = getObjectAt(rockLocations[i]).getID();
				for (final int pid : VirtualMiner.copper) {
					if (id == pid) {
						rockType[i] = VirtualMiner.COPPER;
					}
				}
				for (final int pid : VirtualMiner.tin) {
					if (id == pid) {
						rockType[i] = VirtualMiner.TIN;
					}
				}
				for (final int pid : VirtualMiner.iron) {
					if (id == pid) {
						rockType[i] = VirtualMiner.IRON;
					}
				}
				for (final int pid : VirtualMiner.silver) {
					if (id == pid) {
						rockType[i] = VirtualMiner.SILVER;
					}
				}
				for (final int pid : VirtualMiner.coal) {
					if (id == pid) {
						rockType[i] = VirtualMiner.COAL;
					}
				}
				for (final int pid : VirtualMiner.gold) {
					if (id == pid) {
						rockType[i] = VirtualMiner.GOLD;
					}
				}
				for (final int pid : VirtualMiner.mithril) {
					if (id == pid) {
						rockType[i] = VirtualMiner.MITHRIL;
					}
				}
				for (final int pid : VirtualMiner.adamant) {
					if (id == pid) {
						rockType[i] = VirtualMiner.ADAMANT;
					}
				}
				for (final int pid : VirtualMiner.rune) {
					if (id == pid) {
						rockType[i] = VirtualMiner.RUNE;
					}
				}
				if (rockType[i] == 0) {
					complete = false;
				}
			}
		}
		typesComplete = complete;
	}
}